package cn.ibizlab.util.helper;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;

import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;


public class JacksonUtils {

    private JacksonUtils() {
        throw new IllegalStateException("Utility class");
    }

    public static final ObjectMapper mapper = new ObjectMapper();

    static {
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        mapper.setSerializationInclusion(Include.NON_NULL);
    }



    @SneakyThrows
    public static String toJson(Object obj) {
        return mapper.writeValueAsString(obj);
    }

    @SneakyThrows
    public static <T> T toObj(Object obj,Class<T> clazz)
    {
        if(obj==null)
            return null;
        else if(obj instanceof String)
            return mapper.readValue(obj.toString(), clazz);
        else
            return mapper.readValue(mapper.writeValueAsString(obj), clazz);
    }


    @SneakyThrows
    public static <T> T toObj(InputStream inputStream, Class<T> cls) {
        return mapper.readValue(inputStream, cls);
    }


    @SneakyThrows
    public static <T> T toObj(String json, Class<T> cls) {
        return mapper.readValue(json, cls);
    }

    @SneakyThrows
    public static <T> List<T> toArray(String jsonStr, Class<T> clazz) {
        JavaType javaType = mapper.getTypeFactory().constructCollectionType(List.class, clazz);
        return mapper.readValue(jsonStr, javaType);
    }

    @SneakyThrows
    public static <T> List<T> toArray(Object obj, Class<T> clazz) {
        if(obj==null)
            return Collections.emptyList();
        JavaType javaType = mapper.getTypeFactory().constructCollectionType(List.class, clazz);
        if(obj instanceof String)
            return mapper.readValue(obj.toString(), javaType);
        else
            return mapper.readValue(mapper.writeValueAsString(obj), javaType);
    }

    @SneakyThrows
    public static <T> T toObj(String json, Type type) {
        return mapper.readValue(json, mapper.constructType(type));
    }

    @SneakyThrows
    public static <T> T toObj(String json, TypeReference<T> typeReference) {
        return mapper.readValue(json, typeReference);
    }

    @SneakyThrows
    public static <T> T toObj(InputStream inputStream, Type type) {
        return mapper.readValue(inputStream, mapper.constructType(type));
    }

    @SneakyThrows
    public static JsonNode toObj(String json) {
        return mapper.readTree(json);
    }

    /**
     * Register sub type for child class.
     *
     * @param clz  child class
     * @param type type name of child class
     */
    public static void registerSubtype(Class<?> clz, String type) {
        mapper.registerSubtypes(new NamedType(clz, type));
    }

    /**
     * Create a new empty Jackson {@link ObjectNode}.
     *
     * @return {@link ObjectNode}
     */
    public static ObjectNode createEmptyJsonNode() {
        return new ObjectNode(mapper.getNodeFactory());
    }

    /**
     * Create a new empty Jackson {@link ArrayNode}.
     *
     * @return {@link ArrayNode}
     */
    public static ArrayNode createEmptyArrayNode() {
        return new ArrayNode(mapper.getNodeFactory());
    }

    /**
     * Parse object to Jackson {@link JsonNode}.
     *
     * @param obj object
     * @return {@link JsonNode}
     */
    public static JsonNode transferToJsonNode(Object obj) {
        return mapper.valueToTree(obj);
    }

    /**
     * construct java type -> Jackson Java Type.
     *
     * @param type java type
     * @return JavaType {@link JavaType}
     */
    public static JavaType constructJavaType(Type type) {
        return mapper.constructType(type);
    }
}
